package algos;

import users.UserStatistic;
import users.Users;
import utils.Common;
import utils.Pair;

import java.io.IOException;
import java.util.Collections;
import java.util.List;

import static algos.Engine.WORDS_TO_SUGGEST;
import static utils.Common.makePair;
import static utils.Common.newArrayList;
import static utils.Common.newLinkedList;

public class UserEngine {

    private final List<NGramModel> coreNGramModels;
    private final Vocabulary coreVocabulary;

    public UserEngine(Vocabulary vocabulary, List<NGramModel> coreNGramModels) {
        this.coreNGramModels = coreNGramModels;
        this.coreVocabulary = vocabulary;
    }


    private List<Double> coreCoefs;
    private List<Double> userCoefs;
    private List<Function<Integer, Double>> coefs;

    private static List<Function<Integer, Double>> doubleToConstFunc(List<Double> c) {
        List<Function<Integer, Double>> res = newLinkedList();
        for (Double d : c) {
            res.add(Functions.<Integer, Double>constantFunction(d));
        }
        return res;
    }

    public void setCoefs(List<Double> coreCoefs, List<Double> userCoefs) {
        this.coreCoefs = coreCoefs;
        this.userCoefs = userCoefs;
        coefs = newLinkedList();
        coefs.addAll(doubleToConstFunc(coreCoefs));
        coefs.addAll(doubleToConstFunc(userCoefs));

    }

    public List<Pair<Double, String>> get(List<String> l, String prefix, int userId) {
        UserStatistic userStatistic = Users.getUserStatisticFor(userId, coreVocabulary);
        startPredict(l, coreNGramModels);
        startPredict(l, userStatistic.getAllNGramModels());

        List<EstimationFunction> laplaces = newArrayList();
        for (NGramModel coreNgram : coreNGramModels) {
            laplaces.add(new LaplaceLaw(coreNgram));
        }

        for (int i = 1; i <= Users.userMaxNGramLength(); i++) {
            laplaces.add(new LaplaceLaw(userStatistic.getNGramModelForLength(i)));
        }

        EstimationFunction estimation = new GeneralLinearInterpolation(coefs, laplaces);
        List<Pair<Double, String>> answer = newArrayList(WORDS_TO_SUGGEST);
        for (int word : userStatistic.getVocabulary().getWordsWithPrefix(prefix)) {
            double possib = estimation.p(word);
            if (answer.size() == WORDS_TO_SUGGEST && answer.get(WORDS_TO_SUGGEST - 1).first > -possib) {
                answer.remove(WORDS_TO_SUGGEST - 1);
            }
            if (answer.size() < WORDS_TO_SUGGEST) {
                answer.add(makePair(-possib, userStatistic.getVocabulary().getWordAt(word)));
                Collections.sort(answer);
            }
        }
        for (int i = 0, n = answer.size(); i < n; i++) {
            answer.set(i, makePair(-answer.get(i).first, answer.get(i).second));
        }

        stop(userStatistic.getAllNGramModels());
        stop(coreNGramModels);

        userStatistic.update(l);

        return answer;
    }

    private void startPredict(List<String> words, Iterable<NGramModel> nGrams) {
        for (NGramModel nGramModel : nGrams) {
            nGramModel.startPredict(words);
        }
    }


    private void stop(Iterable<NGramModel> nGrams) {
        for (NGramModel nGramModel : nGrams) {
            nGramModel.stopPredict();
        }
    }


      public static UserEngine create(List<String> ngramfiles) {
        Vocabulary vocabulary = new Vocabulary();
        for (String ngramfile : ngramfiles) {
            vocabulary.addWordsFromFile(ngramfile);
        }
        vocabulary.build();
        List<NGramModel> models = newArrayList();
        for (String ngramfile : ngramfiles) {
            NGramModel ngm = null;
            try {
                ngm = NGramModel.createFrom(ngramfile, vocabulary);
            } catch (IOException e) {
                System.err.println(ngramfile + ": " + e.getMessage());
                e.printStackTrace();
            }
            if (ngm == null) continue;
            models.add(ngm);
        }
        return new UserEngine(vocabulary, models);
    }

}
